package messangerServer;

import messangerTypes.*;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

public class ServerBackend extends Thread
{

	private final Object lock = new Object();
	private MulticastWorker mcw;
	private Map <Integer, WorkerPairContainer> pairs;
	private Map <Integer, Integer> portToUserId;
	private Map <Integer, Integer> userIdToPort;

	
	public ServerBackend () throws IOException, InterruptedException, ClassNotFoundException
	{	
		this.pairs = new HashMap<Integer, WorkerPairContainer> ();
		this.portToUserId = new HashMap <Integer, Integer> ();
		this.userIdToPort = new HashMap <Integer, Integer> ();
		mcw = new MulticastWorker();
		mcw.start();
		
		Class.forName(MessangerSettings.getJdbcDriver());
		
		//for debug seed of the multicast announcement!
		/*
		User one = new User("jane","doe", 0, "");
		User two = new User("john","doe", 1, "");
		User three = new User("frank","henderson", 2, "");
		User four = new User("idont","givafuc", 3, "");

		mcw.addUser(one);
		mcw.addUser(two);
		mcw.addUser(three);
		mcw.addUser(four);
		java.lang.Thread.sleep(1000);*/
	}
	
	synchronized public void readWorkerCallback (Object o, int port)
	{
		MessageBase mb = (MessageBase)o;
		switch(mb.getType())
		{
		case USER_MESSAGE:
			routeMessage(mb, ((UserMessage)mb).getDestination());
			break;
		case CONNECT_REQUEST:
			handleCONNECT_REQUEST(mb, port);
			break;
		case CONNECT_RESPONSE:
		case KEEP_ALIVE:
		case MULTICAST_ANOUNCMENT:
		case NULL:
		default:
			break;
		}
	}
	
	private void handleCONNECT_REQUEST (MessageBase mb, int port)
	{
		ConnectRequest cr = (ConnectRequest) mb;
		if(cr.isLogin())
		{
			login(cr.getRequestingUser(), port);
		}
		else
		{
			newConversation(cr);
		}
	}
	
	private void login (User user, int port)
	{
		int userId = checkUserAuthenication(user);
		ConnectResponse cr = null;
		
		if(userId != -1)
		{
			User accepted = user;
			accepted.setId(userId);
			cr = new ConnectResponse(ConnectResponse.ResponseCode.ACCEPTED, accepted);
			synchronized (lock)
			{
				userIdToPort.put(userId, port);
				portToUserId.put(port, userId);
			}
			mcw.addUser(accepted);
		}
		else
		{
			cr = new ConnectResponse(ConnectResponse.ResponseCode.REVOKED, user);
		}
		
		pairs.get(port).sendWorker.write(cr);		
	}
	
	//bad this is going to be IO bound. should be a separate thread with and event queue
	private int checkUserAuthenication (User user)
	{
		Connection sqlCon = null;
		Statement stmt = null;
		ResultSet rs = null;
		int userID = -1;
		try
		{
			sqlCon = DriverManager.getConnection(MessangerSettings.getDbUrl(), 
					MessangerSettings.getSqlusername(),
					MessangerSettings.getSqlpassword());
			
			System.out.println("Creating statement...");
			stmt = sqlCon.createStatement();
			String sql;
			sql = "SELECT * FROM "+MessangerSettings.getTablepath()+
					" WHERE lastName = '"+user.getNameLast()+"' and firstName = '"
					+user.getNameFirst()+"';";
			System.out.println(sql);
			rs = stmt.executeQuery(sql);

			if(rs.next())
				if(!rs.wasNull())
					if(user.getPassword().contentEquals(rs.getString("password")))
						userID = rs.getInt("id");
			
			rs.close();
			stmt.close();
			sqlCon.close();
		} 
		catch (SQLException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
			
		return userID;
	}
	
	private void newConversation (ConnectRequest cr)
	{
		WorkerPairContainer requester = null;
		WorkerPairContainer requesty = null;
		ConnectResponse resp = null;
		
		synchronized (lock)
		{
			//prevent conversation between 2 users that are not logged in
			requester = pairs.get(userIdToPort.get(cr.getRequestedUser().getId()));
			requesty = pairs.get(userIdToPort.get(cr.getRequestingUser().getId()));
		}
		
		if(requester != null &&
				requesty != null)
		{
			resp = new ConnectResponse(ConnectResponse.ResponseCode.ACCEPTED, 
					cr.getRequestingUser(), 
					cr.getRequestedUser());
		}
		else
		{
			resp = new ConnectResponse(ConnectResponse.ResponseCode.REVOKED, 
					cr.getRequestingUser(), 
					cr.getRequestedUser());
		}
		routeMessage(resp, resp.getRequestingUser());

	}
	
	private void routeMessage (MessageBase mb, User destination)
	{
		synchronized (lock)
		{
			WorkerPairContainer pair = pairs.get(userIdToPort.get(destination.getId()));
			
			if(pair != null)
			{
				pair.sendWorker.write(mb);
			}
			else
			{
				//TODO: add logic to send a fail message
			}
		}
	}
	
	public void clientDisconnect (int port)
	{
		synchronized (lock)
		{
			WorkerPairContainer current = pairs.get(port);
			pairs.remove(port);

			current.readWorker.quit = true;
			current.sendWorker.quit = true;
			
			int userId = portToUserId.get(port);
			
			portToUserId.remove(port);
			userIdToPort.remove(userId);
			
			mcw.removeUser(userId);
		}
	}
	
	public void run()
	{
		try
		{
			ServerSocket listeningSock = new ServerSocket(MessangerSettings.getUcastport());
			ServerUnicastSendWorker sendWorker = null;
			ServerUnicastReadWorker readWorker = null;
			Socket client = null;
			WorkerPairContainer workers;
			int port;
			
			while (true)
			{
				client = listeningSock.accept();
				port = client.getPort();
				System.out.println("ServerBacken: client port = "+ port);
				readWorker = new ServerUnicastReadWorker(this, port, client.getInputStream());
				sendWorker = new ServerUnicastSendWorker(this, port, client.getOutputStream());
				readWorker.start();
				sendWorker.start();
				workers = new WorkerPairContainer(sendWorker, readWorker);
				
				synchronized (lock)
				{
					pairs.put(port, workers);
				}
			}
		} catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}	
}
